package com.fantaike.algorithm.tree;

import com.fantaike.algorithm.linear.Queue;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * 二叉查找树：插入、删除、查找最小、最大的键、前中后层序遍历、最大深度
 * @author jishushen
 * @create 2021-11-07 14:24
 */
public class BinaryTree<Key extends Comparable<Key>,Value> {

    //记录根节点
    private Node<Key,Value> root;
    //记录元素中的个数
    private int N;

    private class Node<Key,Value>{
        //存储键
        public Key key;
        //存储值
        private Value value;
        //记录左子结点
        public Node left;
        //记录右子结点
        public Node right;
        public Node(Key key, Value value, Node left, Node right) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
        }
    }
    //元素中的个数
    public int size(){
        return N;
    }
    //向树中添加元素key-value
    public void put(Key key,Value value){
        root = put(this.root, key, value);
    }

    //向指定的树x中添加key-value,并返回添加元素后的新树
    private Node<Key,Value> put(Node<Key,Value> x,Key key,Value value){
        //如果x为null，说明没有节点，则就向此节点之间插入
        if (x == null){
            //元素个数加1
            N++;
            return new Node<Key,Value>(key,value,null,null);
        }

        //传来的x不为空，比较x.key与传来key值的大小
        int cmp = key.compareTo(x.key);
        if (cmp > 0){
            //说明key的值大于x节点键的值的，则继续找x节点的右子树
            x.right = put(x.right, key, value);
        }else if(cmp < 0){
            //说明key的值小于x节点键的值，继续找x的左子树
            x.left = put(x.left,key,value);
        }else {
            //说明key的值和x节点的键是相等的，那么直接替换value
            x.value = value;
        }
        return x;
    }
    //查询树中指定key对应的value
    public Value get(Key key){
        return get(root,key);
    }

    private Value get(Node<Key,Value> x, Key key){
        //如果x树为null,就直接返回null
        if (x == null){
            return null;
        }
        //x树不为null
        //比较key和x节点键的大小
        int cmp = key.compareTo(x.key);
        if (cmp > 0){
            //说明key的值大于x节点键的值的，则继续找x节点的右子树
            return (Value) get(x.right,key);
        }else if(cmp < 0){
            //说明key的值小于x节点键的值，继续找x的左子树
            return (Value) get(x.left,key);
        }else {
            //说明key的值和x节点的键是相等的，那么直接替换value
            return x.value;
        }
    }

    //删除树中对应key的value
    public void delete(Key key){
        root = delete(root, key);
    }

    //删除指定树x中的key对应的value，并返回删除后的新树
    private Node<Key,Value> delete(Node<Key,Value> x,Key key){
        //判断树x是否有值
        if (x == null){
            return null;
        }

        //x树不为null
        //比较key和x节点键的大小
        int cmp = key.compareTo(x.key);
        if (cmp > 0){
            //说明key的值大于x节点键的值的，则继续找x节点的右子树
            x.right = delete(x.right,key);
        }else if(cmp < 0){
            //说明key的值小于x节点键的值，继续找x的左子树
            x.left = delete(x.left,key);
        }else {
            //说明key的值和x节点的键是相等的，当前x就是要删除的结点
            //1.如果当前结点的右子树不存在，则直接返回当前结点的左子结点
            if (x.right == null) {
                return x.left;
            }
            //2.如果当前结点的左子树不存在，则直接返回当前结点的右子结点
            if (x.left == null) {
                return x.right;
            }
            //3.当前结点的左右子树都存在
            //3.1找到右子树中最小的结点
            Node minNode = x.right;
            //minNode最小的节点为左子树最后一个值
            while (minNode.left != null){
                minNode = minNode.left;
            }

            //3.2删除右子树中最小的结点
            Node n = x.right;
            while (n.left != null){
                if (n.left.left == null){
                    n.left = null;
                }else {
                    n = n.left;
                }
            }

            //3.3让被删除结点的左子树称为最小结点minNode的左子树，让被删除结点的右子树称为最小结点minNode的右子树
            minNode.left = x.left;
            minNode.right = x.right;
            //3.4让被删除结点的父节点指向最小结点minNode
            x = minNode;
            //个数-1
            N--;
        }
        return x;
    }

    /**
     * 在某些情况下，我们需要查找出树中存储所有元素的键的最小值，比如我们的树中存储的是学生的排名和姓名数
     * 据，那么需要查找出排名最低是多少名？这里我们设计如下两个方法来完成：
     */
    //找出树中最小的键
    public Key min(){
        return (Key) min(this.root).key;
    }

    //找出指定树x中，最小键所在的结点
    private Node min(Node x){
        Node n = x.left;
        while (n != null){
            n = n.left;
        }
        if (n == null){
            return x;
        }
        return n;
    }

    /**
     * 在某些情况下，我们需要查找出树中存储所有元素的键的最大值，比如比如我们的树中存储的是学生的成绩和学生
     * 的姓名，那么需要查找出最高的分数是多少？这里我们同样设计两个方法来完成：
     */
    //找出整个树中最大的键
    public Key max(){
        return (Key) max(root).key;
    }
    //找出指定树x中最大键所在的结点
    public Node max(Node x){
        if (x.right!=null){
            return max(x.right);
        }else{
            return x;
        }
    }

    /**
     * 使用前序遍历，获取整个树中的所有
     * @Date: 2022/6/10 16:18
     * @Author: xuepeng
     * @Return
     * @Throws
     */
    public Queue<Key> preErgodic(){
        Queue<Key> queues = new Queue<>();
        this.preErgodic(root,queues);
        return queues;
    }

    //使用前序遍历，把指定树x中的所有键放入到keys队列
    private void preErgodic(Node x,Queue<Key> keys){
        //判断x是否我null
        if (x == null){
            return;
        }
        //把当前结点的key放入到队列中
        keys.enqueue((Key) x.key);
        //2.找到当前结点的左子树，如果不为空，递归遍历左子树
        if (x.left != null){
            this.preErgodic(x.left,keys);
        }

        //3.找到当前结点的右子树，如果不为空，递归遍历右子树
        if (x.right != null){
            this.preErgodic(x.right,keys);
        }
    }

    //使用中序遍历，获取整个树中的所有键
    public Queue<Key> midErgodic(){
        Queue<Key> keys = new Queue<Key>();
        this.midErgodic(root,keys);
        return keys;
    }

    //使用中序遍历，把指定树x中的所有键放入到keys队列中
    private void midErgodic(Node x,Queue<Key> keys){
        if (x == null){
            return;
        }

        //1.找到当前结点的左子树，如果不为空，递归遍历左子树
        if (x.left != null){
            this.midErgodic(x.left,keys);
        }
        //2.把当前结点的key放入到队列中;
        keys.enqueue((Key) x.key);
        //3.找到当前结点的右子树，如果不为空，递归遍历右子树
        if (x.right != null){
            this.midErgodic(x.right,keys);
        }
    }

    //后序遍历
    public Queue<Key> afterErgodic(){
        Queue<Key> keys = new Queue<>();
        this.afterErgodic(root,keys);
        return keys;
    }

    //使用后序遍历，把指定树x中的所有键放入到keys队列中
    private void afterErgodic(Node x,Queue<Key> keys){
        if (x == null){
            return;
        }
        //1.找到当前结点的左子树，如果不为空，递归遍历左子树
        if (x.left != null){
            this.afterErgodic(x.left, keys);
        }
        //2.找到当前结点的右子树，如果不为空，递归遍历右子树
        if (x.right != null){
            this.afterErgodic(x.right,keys);
        }
        //3.把当前结点的key放入到队列中
        keys.enqueue((Key) x.key);
    }

    /**
     * 层序遍历：所谓的层序遍历，就是从根节点（第一层）开始，依次向下，获取每一层所有结点的值；
     * 使用层序遍历，获取整个树中的所有键
     * @Date: 2022/6/10 18:45
     * @Author: xuepeng
     * @Return
     * @Throws
     */
    public Queue<Key> layerErgodic(){
        //存所有的键
        Queue<Key> keys = new Queue<>();
        //存树中的节点
        Queue<Node> nodes = new Queue<>();
        //默认，存根节点
        nodes.enqueue(root);
        while (!nodes.isEmpty()){
            //从队列中弹出一个节点，把key放入到keys中
            Node n = nodes.dequeue();
            keys.enqueue((Key) n.key);
            //如果当前结点的左子结点不为空，则把左子结点放入到队列中
            if (n.left != null){
                nodes.enqueue(n.left);
            }
            //如果当前结点的右子结点不为空，则把右子结点放入到队列中
            if (n.right != null){
                nodes.enqueue(n.right);
            }
        }
        return keys;
    }

    /**
     * 给定一棵树，请计算树的最大深度（树的根节点到最远叶子结点的最长路径上的结点数）;
     * @Date: 2022/6/11 17:03
     * @Author: xuepeng
     */
    public int maxDepth(){
        return maxDepth(root);
    }

    //计算指定树x的最大深度
    private int maxDepth(Node x){
        //如果x节点为空，返回0
        if (x == null){
            return 0;
        }
        //定义最大深度
        int max = 0;
        //定义左子树最大深度
        int maxL = 0;
        //定义右子树最大深度
        int maxR = 0;

        //计算左子树最大深度
        if (x.left != null){
            maxL = this.maxDepth(x.left);
        }

        //计算右子树最大深度
        if (x.right != null){
            maxR = this.maxDepth(x.right);
        }
        max = maxL >= maxR ? maxL + 1 : maxR + 1;
        return max;
    }

}
